using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Apache.Cassandra;
using SKBKontur.Cassandra.Logging.Helpers;
using SKBKontur.Cassandra.Proxy;
using SKBKontur.Logging;
using SKBKontur.Logging.Model;

namespace SKBKontur.Cassandra.Logging.Storage
{
    public class CassandraLogEventWriter : ILogEventWriter
    {
        private readonly ICassandraLogParameters logParameters;
        private readonly ICassandraLoggingConfigurator configurator;

        public CassandraLogEventWriter(ICassandraLogParameters logParameters, ICassandraLoggingConfigurator configurator)
        {
            this.logParameters = logParameters;
            this.configurator = configurator;
        }

        #region ILogEventWriter Members

        public void Create<T>(T logEvent) where T : LogEvent
        {
            using (IStorage storage = configurator.StorageFactory.CreateStorage(configurator.KeySpace))
            {
                byte[] key = configurator.KeyGenerator.GetKey(logEvent.TimeStamp);

                AppendEvent(key, logEvent, storage);

                if (logEvent.LogException != null)
                    AppendException(key, logEvent, storage);

                EventAppendMaster(key, logEvent, storage);
            }
        }

        #endregion

        private void EventAppendMaster(byte[] recordKey, LogEvent logEvent, IStorage storage)
        {
            byte[] masterKey = configurator.KeyGenerator.GetMasterKey(logEvent.TimeStamp);

            byte[] columnValue = SplitAndJoin.SetCF(logEvent.LogException, logParameters.EventCF, logParameters.ExceptionCF);

            int ttl = 0;
            if ((logParameters.EventTtl > 0) && (logParameters.ExceptionTtl > 0))
            {
                ttl = Math.Max(logParameters.EventTtl, logParameters.ExceptionTtl);
            }

            var column = new Column
                             {
                                 Name = recordKey,
                                 Value = columnValue,
                                 Timestamp = logEvent.TimeStamp.Ticks
                             };
            if (ttl > 0)
            {
                column.Ttl = ttl;
            }

            var columnParent = new List<Column>
                                   {
                                       column
                                   };

            storage.Insert(masterKey,
                         new ColumnParent
                             {
                                 Column_family = configurator.MasterCF,
                             },
                         configurator.WriteConsistencyLevel, columnParent.ToArray());
        }

        private void AppendException(byte[] recordKey, LogEvent logEvent, IStorage storage)
        {
            IEnumerable<Column> columns = DictToColumns(configurator.Serializer.Serialize(logEvent.LogException), logParameters.ExceptionTtl,
                                                        logEvent.TimeStamp);
            storage.Insert(recordKey,
                         new ColumnParent
                             {
                                 Column_family = logParameters.ExceptionCF,
                             },
                         configurator.WriteConsistencyLevel, columns.ToArray());
        }

        private static IEnumerable<Column> DictToColumns(Dictionary<string, byte[]> columns, int ttl, DateTime timeStamp)
        {
            foreach (var column in columns)
                yield return CreateColumn(column.Key, column.Value, timeStamp.Ticks, ttl);
        }

        private void AppendEvent<T>(byte[] recordKey, T logEvent, IStorage storage) where T : LogEvent
        {
            IEnumerable<Column> columns = DictToColumns(configurator.Serializer.Serialize(logEvent), logParameters.EventTtl, logEvent.TimeStamp);
            storage.Insert(recordKey,
                         new ColumnParent
                             {
                                 Column_family = logParameters.EventCF,
                             },
                         configurator.WriteConsistencyLevel, columns.ToArray());
        }

        private static Column CreateColumn(string name, byte[] value, long timestamp, int ttl)
        {
            var temp = new Column
                           {
                               Name = Encoding.UTF8.GetBytes(name),
                               Value = value,
                               Timestamp = timestamp
                           };
            if (ttl > 0)
            {
                temp.Ttl = ttl;
            }

            return temp;
        }
    }
}